/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grex.fitnessfunctions.Classification;

import grex.GP;
import grex.Options;
import grex.Prediction;
import grex.fitnessfunctions.IFitnessFunction;
import grex.PredictionContainer;
import grex.fitnessfunctions.FitnessFunction;
import grex.genes.Gene;
import grex.genes.GeneException;
import grex.genes.HybridPred;
import grex.genes.ITerminal;
import grex.genes.If;

import java.io.Serializable;

/**
 *
 * @author RIK
 */
public class HybridFitness extends FitnessFunction implements IFitnessFunction, Serializable {
    
    public HybridFitness(){
        super(FitnessFunction.HYB);
    }
    
    /**
     * Calculates prediction error
     * @param prediction Prediction object.
     * @param targetValue Target value.
     * @return Prediction error weighted based on Gene class.
     */
    @Override
    public double calcPredictionError(Prediction prediction, double targetValue) {
        if(prediction.getGene() instanceof HybridPred)
        {
            return prediction.getPrediction() == targetValue ? options.getPARAMETER_1() : 1;
        }//*/
       return prediction.getPrediction() == targetValue ? 0 : 1;
    }
    
    /**
     * Calculates parsimony pressure.
     * @param gp GP object
     * @return Parsimony pressure based on brevity and punishment for length
     */
    @Override
    protected double calcParismonyPressure(GP gp){
        double pressure=0;        
        if(gp.getNrOfNodes()>options.getMAX_TREE_SIZE())
            pressure = (gp.getNrOfNodes()- options.getMAX_TREE_SIZE()) * 2;
        pressure += /*calcRF(gp.getHead(),0) +*/ gp.getNrOfNodes() * options.getPUNISHMENT_FOR_LENGTH();
        if(calcRF(gp.getHead(), 0) != 1)
            pressure += 100;
        return pressure;
    }
    
    /**
     * Calculates final error.
     * @param pc PredictionContainer
     * @return Final error value.
     */
    @Override
    protected double calcFinalError(PredictionContainer pc){
        if(gp==null)
            return -999;
        return calcBrevity(gp.getHead(), 0)/getNrOfInstances();
    }
    
    
    /* 
       This method is overridden since brevity does not measure accuracy.
       Hence the reported error is the brevity but accuracy still needs to 
       be a part of the fitness function since brevity disregards 
       predicitve performance.     
     */
    /**
     * Calculates fitness for GP
     * @param gp GP to calculate fitness on.
     * @return Fitness value based on total errors, parsimony pressure and length punishment
     * @throws GeneException 
     */
    @Override
    public double calcFitness(GP gp) throws GeneException{
        gp.train();
        this.gp=gp;
        options = gp.getOptions();
        this.pc=gp.getPcTrain();
         
        return normalizeTotalError(calcTotalError(pc))
                + calcParismonyPressure(gp)
                 * options.getPUNISHMENT_FOR_LENGTH();
    }
    
    private double calcBrevity(Gene gene,int depth){
        double brevity=0;
        Gene[] children = gene.getChildren();
        if(children != null){
            int i = 0;
            if(gene instanceof If)
                i++;
            for (; i < children.length; i++) {
                brevity+=calcBrevity(children[i],depth +1);
            }
        }else{
            brevity = ((ITerminal)gene).getCount()*depth;
        }        
        return brevity;        
    }
    
    private double calcRF(Gene gene,int depth){
        double brevity=0;
        Gene[] children = gene.getChildren();
        if(children != null){
           
            for (int i = 0; i < children.length; i++) {
                brevity+=calcRF(children[i],depth +1);
            }
        }else{
            if(gene instanceof HybridPred)
            brevity++;
        }        
        return brevity;        
    }
}
